Skip to content

Conversation

@anish-work
Copy link
Contributor

@anish-work anish-work commented Oct 29, 2025

Q/A checklist

  • I have tested my UI changes on mobile and they look acceptable
  • I have tested changes to the workflows in both the API and the UI
  • I have done a code review of my changes and looked at each line of the diff + the references of each function I have changed
  • My changes have not increased the import time of the server
How to check import time?

time python -c 'import server'

You can visualize this using tuna:

python3 -X importtime -c 'import server' 2> out.log && tuna out.log

To measure import time for a specific library:

$ time python -c 'import pandas'

________________________________________________________
Executed in    1.15 secs    fish           external
   usr time    2.22 secs   86.00 micros    2.22 secs
   sys time    0.72 secs  613.00 micros    0.72 secs

To reduce import times, import libraries that take a long time inside the functions that use them instead of at the top of the file:

def my_function():
    import pandas as pd
    ...

Legal Boilerplate

Look, I get it. The entity doing business as “Gooey.AI” and/or “Dara.network” was incorporated in the State of Delaware in 2020 as Dara Network Inc. and is gonna need some rights from me in order to utilize my contributions in this PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Dara Network Inc can use, modify, copy, and redistribute my contributions, under its choice of terms.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

📝 Walkthrough

Walkthrough

Introduces a sidebar-driven Gooey Builder UX: adds widgets/sidebar.py (SidebarRef, use_sidebar, sidebar_layout) for per-session sidebar and responsive layout; splits daras_ai_v2.gooey_builder into launcher and inline entry points with access gating (can_launch_gooey_builder) and onClose wiring; updates daras_ai_v2/base.py to add BasePage.render_sidebar and route builder rendering through the new launcher/inline/sidebar flow; adapts routers/root.py to render a two-pane layout and integrate the builder launcher.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • devxpy
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Bot builder in sidebar' accurately summarizes the main change—moving the Gooey builder UI into a sidebar layout with launcher and inline rendering flows.
Description check ✅ Passed The pull request description matches the required template with all checklist items completed and the legal boilerplate intact.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
daras_ai_v2/gooey_builder.py (2)

3-3: Defer heavy Django model import to reduce server import time

Importing BotIntegration at module import time can slow startup. Lazily import inside the functions instead.

Apply this diff:

-from bots.models import BotIntegration
+# Defer heavy Django model import to function scope to reduce import time

And inside both functions:

 def render_gooey_builder_inline(page_slug: str, builder_state: dict):
+    from bots.models import BotIntegration
     if not settings.GOOEY_BUILDER_INTEGRATION_ID:
         return
@@
 def render_gooey_builder(page_slug: str, builder_state: dict):
+    from bots.models import BotIntegration
     if not settings.GOOEY_BUILDER_INTEGRATION_ID:
         return

Also applies to: 11-15, 66-74


12-14: Avoid hard-coding hostname; derive at runtime or accept a parameter

Using hostname="gooey.ai" may break staging/custom hosts and Google Maps API hostname checks. Consider deriving from settings.APP_BASE_URL or passing hostname from the caller.

Example:

from furl import furl
hostname = furl(settings.APP_BASE_URL).host or "gooey.ai"
config = bi.get_web_widget_config(hostname=hostname, target="#gooey-builder-embed")

Also applies to: 16-24, 30-33

daras_ai_v2/base.py (1)

417-446: Unify default sidebar open state with routers.root to avoid first-load flicker

render_sidebar uses use_sidebar("builder-sidebar") with default default_open=True, but routers.root initializes the same key with default_open=False. Consider aligning defaults in both places (False) to prevent state desync on first load.

Would you like a small patch to centralize this default in a constant?

widgets/sidebar.py (3)

41-47: Use dict.pop instead of membership test + del (RUF051)

Simplify and avoid KeyError race.

Apply this diff:

-    if current_time - last_load_time > 0.5:
-        # Fresh page load - clear mobile state
-        mobile_key = key + ":mobile"
-        if mobile_key in session:
-            del session[mobile_key]
+    if current_time - last_load_time > 0.5:
+        # Fresh page load - clear mobile state
+        mobile_key = key + ":mobile"
+        session.pop(mobile_key, None)

127-134: Remove unused enumerate index (B007)

The loop counter i is unused.

Apply this diff:

-def sidebar_item_list(is_sidebar_open, current_url=None):
-    for i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS):
+def sidebar_item_list(is_sidebar_open, current_url=None):
+    for url, label, icon in settings.SIDEBAR_LINKS:

41-43: Fix misleading comment about time threshold

Comment says “1 second” but code uses 0.5s.

Update to: “If more than 0.5 seconds has passed since last load…”

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6172b8 and ac831ba.

📒 Files selected for processing (4)
  • daras_ai_v2/base.py (4 hunks)
  • daras_ai_v2/gooey_builder.py (1 hunks)
  • routers/root.py (4 hunks)
  • widgets/sidebar.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
daras_ai_v2/gooey_builder.py (1)
bots/models/bot_integration.py (1)
  • get_web_widget_config (565-592)
routers/root.py (4)
widgets/sidebar.py (2)
  • sidebar_layout (210-322)
  • use_sidebar (33-60)
daras_ai_v2/base.py (3)
  • render_sidebar (417-445)
  • render (368-415)
  • current_workspace (1467-1472)
routers/account.py (1)
  • explore_in_current_workspace (140-156)
workspaces/widgets.py (1)
  • global_workspace_selector (21-161)
widgets/sidebar.py (1)
routers/root.py (1)
  • anonymous_login_container (853-882)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (66-121)
  • render_gooey_builder_inline (7-63)
widgets/sidebar.py (3)
  • sidebar_layout (210-322)
  • use_sidebar (33-60)
  • set_open (21-22)
🪛 Ruff (0.14.2)
widgets/sidebar.py

46-46: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)


128-128: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
daras_ai_v2/base.py (1)

1252-1274: Do not pass entire session_state to the widget; use the filtered approach in the commented code.

This change still passes gui.session_state wholesale to the client widget (line 1254), which can expose unrelated or sensitive session data. The previous review flagged this exact issue, and the commented code (lines 1255-1274) demonstrates the safer, filtered approach.

Apply this diff to restore the filtered payload:

-        render_gooey_builder_inline(self.slug_versions[-1], gui.session_state)
-        # render_gooey_builder(
-        #     page_slug=self.slug_versions[-1],
-        #     builder_state=dict(
-        #         status=dict(
-        #             error_msg=gui.session_state.get(StateKeys.error_msg),
-        #             run_status=gui.session_state.get(StateKeys.run_status),
-        #             run_time=gui.session_state.get(StateKeys.run_time),
-        #         ),
-        #         request=extract_model_fields(
-        #             model=self.RequestModel, state=gui.session_state
-        #         ),
-        #         response=extract_model_fields(
-        #             model=self.ResponseModel, state=gui.session_state
-        #         ),
-        #         metadata=dict(
-        #             title=self.current_pr.title,
-        #             description=self.current_pr.notes,
-        #         ),
-        #     ),
-        # )
+        builder_state = dict(
+            status=dict(
+                error_msg=gui.session_state.get(StateKeys.error_msg),
+                run_status=gui.session_state.get(StateKeys.run_status),
+                run_time=gui.session_state.get(StateKeys.run_time),
+            ),
+            request=extract_model_fields(
+                model=self.RequestModel, state=gui.session_state
+            ),
+            response=extract_model_fields(
+                model=self.ResponseModel, state=gui.session_state
+            ),
+            metadata=dict(
+                title=self.current_pr.title,
+                description=self.current_pr.notes,
+            ),
+        )
+        render_gooey_builder_inline(self.slug_versions[-1], builder_state)
🧹 Nitpick comments (1)
daras_ai_v2/gooey_builder.py (1)

7-24: Consider documenting the expected structure and security implications of builder_state.

The builder_state parameter accepts any dict and passes it to the client widget via variables. Adding a docstring or type hint clarifying the expected structure (e.g., status/request/response/metadata) and noting that sensitive data should be filtered out would help prevent misuse.

Example:

 def render_gooey_builder_inline(page_slug: str, builder_state: dict):
+    """
+    Render the Gooey builder widget in inline mode.
+    
+    Args:
+        page_slug: The workflow page slug
+        builder_state: Filtered state dict containing only non-sensitive data
+            (e.g., status, request, response, metadata). Do not pass raw session_state.
+    """
     if not settings.GOOEY_BUILDER_INTEGRATION_ID:
         return
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac831ba and ead2b18.

📒 Files selected for processing (2)
  • daras_ai_v2/base.py (4 hunks)
  • daras_ai_v2/gooey_builder.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (70-125)
  • render_gooey_builder_inline (7-67)
widgets/sidebar.py (3)
  • sidebar_layout (210-322)
  • use_sidebar (33-60)
  • set_open (21-22)
daras_ai_v2/gooey_builder.py (1)
bots/models/bot_integration.py (1)
  • get_web_widget_config (565-592)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (python)
  • GitHub Check: test (3.10.12, 1.8.3)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
daras_ai_v2/base.py (1)

1282-1284: Do not pass entire session_state to the widget; filter to required fields only.

Passing gui.session_state wholesale to render_gooey_builder_inline can inadvertently expose unrelated or sensitive data to the client widget. The commented-out code below (lines 1286-1305) shows the safer, filtered approach.

Apply the diff from the previous review to restore a filtered payload:

 if not self.is_current_user_admin():
     return
-render_gooey_builder_inline(self.slug_versions[-1], gui.session_state)
+builder_state = dict(
+    status=dict(
+        error_msg=gui.session_state.get(StateKeys.error_msg),
+        run_status=gui.session_state.get(StateKeys.run_status),
+        run_time=gui.session_state.get(StateKeys.run_time),
+    ),
+    request=extract_model_fields(
+        model=self.RequestModel, state=gui.session_state
+    ),
+    response=extract_model_fields(
+        model=self.ResponseModel, state=gui.session_state
+    ),
+    metadata=dict(
+        title=self.current_pr.title,
+        description=self.current_pr.notes,
+    ),
+)
+render_gooey_builder_inline(self.slug_versions[-1], builder_state)

This preserves the inline experience while avoiding broad session exposure.

Based on past review comments.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df49769 and df76b4d.

📒 Files selected for processing (1)
  • daras_ai_v2/base.py (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (70-125)
  • render_gooey_builder_inline (7-67)
widgets/sidebar.py (4)
  • sidebar_layout (210-322)
  • use_sidebar (33-60)
  • set_open (21-22)
  • set_mobile_open (24-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test (3.10.12, 1.8.3)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (2)
daras_ai_v2/base.py (2)

48-48: LGTM: Imports support the new sidebar functionality.

The new imports are necessary for the sidebar integration. Note that sidebar_layout is imported at line 85 but not used in this file—it's likely consumed in routers/root.py as mentioned in the AI summary.

Also applies to: 85-85


419-479: render_sidebar() is properly integrated and invoked.

Verification confirms the method is called from routers/root.py line 740 as container.render_sidebar(). The implementation correctly restricts the builder to the run tab, manages state transitions, and handles both desktop and mobile views. No issues found.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
daras_ai_v2/base.py (1)

1291-1310: Remove commented code.

As noted in the previous review, this commented block should be removed to improve code cleanliness. The inline rendering approach is now established, and keeping the old implementation as a comment adds unnecessary clutter.

Apply this diff to remove the commented code:

-
-        # render_gooey_builder(
-        #     page_slug=self.slug_versions[-1],
-        #     builder_state=dict(
-        #         status=dict(
-        #             error_msg=gui.session_state.get(StateKeys.error_msg),
-        #             run_status=gui.session_state.get(StateKeys.run_status),
-        #             run_time=gui.session_state.get(StateKeys.run_time),
-        #         ),
-        #         request=extract_model_fields(
-        #             model=self.RequestModel, state=gui.session_state
-        #         ),
-        #         response=extract_model_fields(
-        #             model=self.ResponseModel, state=gui.session_state
-        #         ),
-        #         metadata=dict(
-        #             title=self.current_pr.title,
-        #             description=self.current_pr.notes,
-        #         ),
-        #     ),
-        # )

Based on learnings

🧹 Nitpick comments (1)
daras_ai_v2/base.py (1)

419-466: Consider adding a docstring to document sidebar integration.

The render_sidebar method is a new public method but lacks documentation explaining when it's called and its integration with the page wrapper/layout system.

Apply this diff to add documentation:

 def render_sidebar(self):
+    """
+    Render an admin-only sidebar containing the Gooey builder interface.
+    
+    This method is called by the page wrapper to render sidebar content.
+    The sidebar is only displayed on the run tab for admin users and provides
+    an inline builder interface for workflow configuration.
+    """
     if not self.is_current_user_admin():
         return
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf968b2 and 2dfeb70.

📒 Files selected for processing (1)
  • daras_ai_v2/base.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (70-125)
  • render_gooey_builder_inline (7-67)
widgets/sidebar.py (4)
  • sidebar_layout (210-325)
  • use_sidebar (33-60)
  • set_open (21-22)
  • set_mobile_open (24-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (1)
daras_ai_v2/base.py (1)

1270-1289: Security concern from past review has been addressed.

The current implementation correctly filters builder_state to only include necessary fields (status, request, response, metadata) rather than passing the entire gui.session_state. The use of extract_model_fields ensures only model-defined fields are sent to the client widget.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
routers/root.py (1)

823-828: Fix className concatenation bug (regression from past review).

Despite the past review comment indicating this was addressed, the className parameter concatenation issue remains:

  1. When the sidebar is closed (not is_builder_sidebar_open), the custom className parameter is completely dropped
  2. When the sidebar is open, there's no space between "mx-2 w-100" and the custom className

Apply this diff to properly concatenate the className in both cases:

             with gui.div(
                 id="main-content",
-                className="container-xxl "
-                if not is_builder_sidebar_open
-                else "mx-2 w-100" + className,
+                className=(
+                    "container-xxl " + className
+                    if not is_builder_sidebar_open
+                    else "mx-2 w-100 " + className
+                ),
             ):
🧹 Nitpick comments (1)
routers/root.py (1)

733-733: Consider removing redundant assignment.

The line container = page if page else None is redundant since it simply assigns page to container. You can use page directly in the subsequent code.

Apply this diff to simplify:

-    container = page if page else None
     sidebar_ref = use_sidebar("builder-sidebar", request.session, default_open=False)
     sidebar_content, pane_content = sidebar_layout(sidebar_ref)
 
     is_builder_sidebar_open = sidebar_ref.is_open
     with sidebar_content:
-        if container:
-            container.render_sidebar()
+        if page:
+            page.render_sidebar()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2dfeb70 and 377e931.

📒 Files selected for processing (1)
  • routers/root.py (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
routers/root.py (6)
widgets/sidebar.py (3)
  • sidebar_layout (210-325)
  • use_sidebar (33-60)
  • set_mobile_open (24-26)
daras_ai_v2/base.py (3)
  • render_sidebar (419-465)
  • render (370-417)
  • current_workspace (1520-1525)
app_users/models.py (2)
  • is_admin (261-262)
  • save (359-365)
daras_ai_v2/fastapi_tricks.py (1)
  • get_route_path (77-80)
routers/account.py (1)
  • explore_in_current_workspace (140-156)
workspaces/widgets.py (1)
  • global_workspace_selector (21-161)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test (3.10.12, 1.8.3)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (3)
routers/root.py (3)

48-48: LGTM!

The sidebar imports are correctly added to support the new two-pane layout functionality.


705-705: LGTM!

Correctly passes the page instance to enable page-specific sidebar rendering.


835-864: LGTM!

The mobile search button implementation correctly uses responsive classes (d-md-none) to show only on mobile devices, and the toggle logic properly manages visibility.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
daras_ai_v2/base.py (1)

1291-1310: Remove the commented legacy render_gooey_builder call

The commented block with the old non‑inline render_gooey_builder(...) call is now redundant and increases noise. Since the inline path is the active implementation, it’s safe to delete this block.

-        # render_gooey_builder(
-        #     page_slug=self.slug_versions[-1],
-        #     builder_state=dict(
-        #         status=dict(
-        #             error_msg=gui.session_state.get(StateKeys.error_msg),
-        #             run_status=gui.session_state.get(StateKeys.run_status),
-        #             run_time=gui.session_state.get(StateKeys.run_time),
-        #         ),
-        #         request=extract_model_fields(
-        #             model=self.RequestModel, state=gui.session_state
-        #         ),
-        #         response=extract_model_fields(
-        #             model=self.ResponseModel, state=gui.session_state
-        #         ),
-        #         metadata=dict(
-        #             title=self.current_pr.title,
-        #             description=self.current_pr.notes,
-        #         ),
-        #     ),
-        # )
🧹 Nitpick comments (1)
daras_ai_v2/gooey_builder.py (1)

7-68: Guard config.onClose against missing #onClose element

The inline builder wires config.onClose to document.getElementById("onClose").click(), which will throw if the element is absent (e.g., layout changes, multiple builders, or timing edge cases). A small null‑check keeps the widget from crashing when the close target isn’t present.

-    config.onClose = function() {
-        document.getElementById("onClose").click();
-    };
+    config.onClose = function() {
+        const btn = document.getElementById("onClose");
+        if (btn) btn.click();
+    };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 377e931 and aea0371.

📒 Files selected for processing (2)
  • daras_ai_v2/base.py (5 hunks)
  • daras_ai_v2/gooey_builder.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
daras_ai_v2/gooey_builder.py (1)
bots/models/bot_integration.py (2)
  • BotIntegration (163-626)
  • get_web_widget_config (565-592)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (71-126)
  • render_gooey_builder_inline (7-68)
widgets/sidebar.py (3)
  • use_sidebar (33-60)
  • set_open (21-22)
  • set_mobile_open (24-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (1)
daras_ai_v2/base.py (1)

1251-1289: Inline builder wiring and filtered state look good

The updated _render_gooey_builder now calls render_gooey_builder_inline with a filtered builder_state (status, request, response, metadata via extract_model_fields). This avoids sending full session_state to the widget while preserving necessary context for the builder. No changes needed here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
routers/root.py (2)

721-729: Correct page parameter type annotation (currently only allows None)

page: None = None annotates the parameter as literally None, which hides that callers can/should pass a page instance (e.g., BasePage) and degrades type-checker/IDE help.

Consider something like:

-from contextlib import contextmanager
+from contextlib import contextmanager
+from typing import Optional
+from daras_ai_v2.base import BasePage
@@
 def page_wrapper(
     request: Request,
-    page: None = None,
+    page: Optional[BasePage] = None,

(or your preferred union/forward-reference style if circular imports are a concern).


829-835: Fix main-content className concatenation (drops custom classes when sidebar is closed)

Right now:

className="container-xxl "
if not is_builder_sidebar_open
else "mx-2 w-100" + className,
  • Ignores the passed className when the sidebar is closed.
  • Misses a separating space when the sidebar is open.

A more robust pattern:

-            with gui.div(
-                id="main-content",
-                className="container-xxl "
-                if not is_builder_sidebar_open
-                else "mx-2 w-100" + className,
-            ):
+            base_class = (
+                "container-xxl"
+                if not is_builder_sidebar_open
+                else "mx-2 w-100"
+            )
+            extra_class = f" {className}" if className else ""
+            with gui.div(
+                id="main-content",
+                className=base_class + extra_class,
+            ):

so any caller-provided classes are honored in both sidebar states with correct spacing.

🧹 Nitpick comments (1)
routers/root.py (1)

783-799: Hide Gooey builder mobile button on non-recipe pages

The admin-only mobile builder button is rendered for every page using page_wrapper, even when page is None, so on non-recipe pages tapping it can show a blank sidebar.

To avoid dangling UI, you can additionally gate on recipe pages:

-                    if request.user and request.user.is_admin:
+                    if (
+                        is_recipe_page
+                        and request.user
+                        and request.user.is_admin
+                    ):

so the button only appears where the builder actually renders.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aea0371 and 05b09e5.

📒 Files selected for processing (1)
  • routers/root.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • routers/root.py
🧬 Code graph analysis (1)
routers/root.py (2)
widgets/sidebar.py (4)
  • sidebar_layout (210-325)
  • use_sidebar (33-60)
  • set_open (21-22)
  • set_mobile_open (24-26)
daras_ai_v2/base.py (2)
  • render_sidebar (419-465)
  • render (370-417)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (2)
routers/root.py (2)

48-48: Sidebar wiring for recipe pages looks correct

Importing sidebar_layout/use_sidebar and calling page_wrapper(request, page=page, is_recipe_page=True) in render_recipe_page cleanly ties the sidebar state to the concrete page instance; routing and meta-generation remain unchanged and look sound.

Also applies to: 705-712


841-847: Mobile search button container change looks good

Using className="d-flex d-md-none justify-content-end" for the mobile search button container keeps it mobile-only while ensuring proper flex alignment; no functional issues here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
daras_ai_v2/base.py (2)

1291-1310: Remove legacy commented render_gooey_builder block

The fully commented render_gooey_builder(...) block is now redundant given the inline embed path above and adds noise. It’s also the only place in this file that references render_gooey_builder, so keeping it around can confuse future readers.

Consider deleting the entire commented block:

-        # render_gooey_builder(
-        #     page_slug=self.slug_versions[-1],
-        #     builder_state=dict(
-        #         status=dict(
-        #             error_msg=gui.session_state.get(StateKeys.error_msg),
-        #             run_status=gui.session_state.get(StateKeys.run_status),
-        #             run_time=gui.session_state.get(StateKeys.run_time),
-        #         ),
-        #         request=extract_model_fields(
-        #             model=self.RequestModel, state=gui.session_state
-        #         ),
-        #         response=extract_model_fields(
-        #             model=self.ResponseModel, state=gui.session_state
-        #         ),
-        #         metadata=dict(
-        #             title=self.current_pr.title,
-        #             description=self.current_pr.notes,
-        #         ),
-        #     ),
-        # )

(You can then drop the render_gooey_builder import at Line 48 if it’s unused elsewhere.)


419-466: Close handler should also clear mobile sidebar state

In render_sidebar, the "onCloseGooeyBuilder" path (Line 441) only calls sidebar_ref.set_open(False). If the sidebar was opened via is_mobile_open, the mobile flag remains True in the session, so the sidebar can get out of sync or immediately re-open on mobile. The tab != run/preview branch correctly clears both flags; the onClose handler should do the same.

Recommend updating the onClose branch:

-            if gui.session_state.pop("onCloseGooeyBuilder", None):
-                sidebar_ref.set_open(False)
-                raise gui.RerunException()
+            if gui.session_state.pop("onCloseGooeyBuilder", None):
+                sidebar_ref.set_open(False)
+                sidebar_ref.set_mobile_open(False)
+                raise gui.RerunException()

Also double-check any other future close-only branches to ensure both flags are reset.

🧹 Nitpick comments (1)
workspaces/models.py (1)

177-177: Consider adding help_text for admin clarity.

The features field is a generic JSON bag. Adding a help_text would help administrators understand its intended use and expected structure.

-    features = models.JSONField(default=dict)
+    features = models.JSONField(default=dict, help_text="Workspace feature flags and configuration")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05b09e5 and c73660e.

📒 Files selected for processing (5)
  • daras_ai_v2/base.py (5 hunks)
  • usage_costs/migrations/0038_alter_modelpricing_model_name.py (1 hunks)
  • workspaces/admin.py (3 hunks)
  • workspaces/migrations/0013_workspace_features.py (1 hunks)
  • workspaces/models.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • daras_ai_v2/base.py
  • workspaces/models.py
  • workspaces/admin.py
  • usage_costs/migrations/0038_alter_modelpricing_model_name.py
  • workspaces/migrations/0013_workspace_features.py
🧬 Code graph analysis (2)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (71-126)
  • render_gooey_builder_inline (7-68)
widgets/sidebar.py (4)
  • sidebar_layout (210-325)
  • use_sidebar (33-60)
  • set_open (21-22)
  • set_mobile_open (24-26)
workspaces/admin.py (1)
gooeysite/custom_widgets.py (1)
  • JSONEditorWidget (4-26)
🪛 GitHub Actions: Python tests
workspaces/models.py

[error] 175-177: ruff format would reformat 1 file; run 'ruff format' to fix code style issues.

🪛 Ruff (0.14.6)
workspaces/admin.py

135-137: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

usage_costs/migrations/0038_alter_modelpricing_model_name.py

8-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


12-298: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

workspaces/migrations/0013_workspace_features.py

8-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


12-18: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (6)
workspaces/admin.py (2)

135-138: LGTM!

The formfield_overrides configuration correctly maps JSONField to JSONEditorWidget, providing a better editing experience for JSON data in the admin. The static analysis warning (RUF012) about mutable class attributes is a false positive here—this is the standard Django admin pattern and the dict is not mutated at runtime.


86-108: LGTM!

The features field is appropriately placed in the admin fields list and is editable, which is correct for administrator-managed feature configuration.

workspaces/migrations/0013_workspace_features.py (1)

1-18: LGTM!

The migration is correctly generated and aligns with the model change. It properly uses default=dict (callable) to avoid mutable default issues. The static analysis warnings (RUF012) are false positives—Django migrations use class attributes for dependencies and operations by design.

daras_ai_v2/base.py (3)

48-48: Inline builder import matches new usage

Importing render_gooey_builder_inline here aligns with _render_gooey_builder’s new inline usage; the change is consistent and correct.


85-85: Sidebar hooks wired into BasePage

Bringing in use_sidebar for render_sidebar is appropriate and keeps sidebar state tied to the Django session; no issues from this change in this file.


1251-1289: Filtered builder_state with inline embed avoids leaking full session

_render_gooey_builder now:

  • Pops and applies update_gui_state in a constrained way via fields_to_save().
  • Gates access to admins only (is_current_user_admin()).
  • Calls render_gooey_builder_inline with a filtered builder_state composed of:
    • status (error_msg, run_status, run_time)
    • request / response from extract_model_fields
    • Minimal metadata (title, description)

This addresses the earlier concern about passing the entire gui.session_state to the client widget and keeps the payload scoped to what the builder needs.

@anish-work anish-work force-pushed the bot_builder_sidebar branch 2 times, most recently from 95bb0b4 to 05b09e5 Compare December 3, 2025 09:59
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
routers/root.py (3)

734-734: Type annotation still needs correction despite being marked as addressed.

The past review comment flagged page: None = None as incorrect because it only allows None as a value. While marked as addressed in earlier commits, the provided code still shows this issue. The parameter should accept either None or a page instance.

Apply the suggested fix from the previous review:

 def page_wrapper(
     request: Request,
-    page: None = None,
+    page: typing.Optional["BasePage"] = None,
     className="",
     search_filters: typing.Optional[SearchFilters] = None,
     show_search_bar: bool = True,
     is_recipe_page: bool = False,
 ):

Note: You may need to add from __future__ import annotations at the top of the file or import BasePage directly if not already present.


748-752: Mobile sidebar state not considered (duplicate of previous review).

The logic on line 748 sets is_builder_sidebar_open = sidebar_ref.is_open but ignores sidebar_ref.is_mobile_open. This was flagged in a previous review as causing mobile-open sidebars to remain visually open on non-recipe pages.

Apply the suggested fix from the previous review:

-    is_builder_sidebar_open = sidebar_ref.is_open
-    if not is_recipe_page and (is_builder_sidebar_open):
+    is_builder_sidebar_open = sidebar_ref.is_open or sidebar_ref.is_mobile_open
+    if not is_recipe_page and is_builder_sidebar_open:
         sidebar_ref.set_open(False)
         sidebar_ref.set_mobile_open(False)
         raise gui.RerunException()

839-845: className concatenation issue still present (duplicate of previous review).

The past review flagged that when the sidebar is closed, the className parameter is dropped, and when open, spacing before custom classes is missing. While marked as addressed, the provided code still shows this issue.

Apply the suggested fix from the previous review:

             with gui.div(
                 id="main-content",
-                className="container-xxl "
-                if not is_builder_sidebar_open
-                else "mx-2 w-100" + className,
+                className=(
+                    "container-xxl " + className
+                    if not is_builder_sidebar_open
+                    else "mx-2 w-100 " + className
+                ),
             ):
widgets/sidebar.py (1)

296-310: Click-to-open handler issue still present (duplicate of previous review).

The past review flagged that the onClick handler checks event.target.id === "sidebar-click-container" but:

  1. The wrapper div has no id attribute
  2. event.target refers to the clicked element (which may be a child), not the container

This prevents the handler from firing correctly.

Apply the suggested fix from the previous review:

         gui.div(
+            id="sidebar-click-container",
             className="d-flex w-100 h-100 position-relative sidebar-click-container",
             style={"height": "100dvh"},
             onClick=dedent(
                 """
-                if (event.target.id === "sidebar-click-container") {
+                if (event.currentTarget.id === "sidebar-click-container") {
                     document.getElementById("sidebar-hidden-btn").click();
                 }
                 """
                 if not sidebar_ref.is_open
                 else ""
             ),
         ),
🧹 Nitpick comments (5)
daras_ai_v2/gooey_builder.py (1)

7-126: Consider refactoring to eliminate code duplication.

The render_gooey_builder_inline and render_gooey_builder functions share approximately 90% identical code. Only the mode, showRunLink, div style, and onClose handler differ between them.

🔎 Consider this refactoring approach:

Extract common logic into a helper function and parameterize the differences:

def _render_gooey_builder_common(
    page_slug: str, 
    builder_state: dict, 
    mode: str = "default",
    show_run_link: bool = False,
    div_style: str = "",
    on_close_handler: str = ""
):
    """Common logic for rendering Gooey builder."""
    if not settings.GOOEY_BUILDER_INTEGRATION_ID:
        return

    bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
    config = bi.get_web_widget_config(
        hostname="gooey.ai", target="#gooey-builder-embed"
    )

    config["mode"] = mode
    if show_run_link:
        config["showRunLink"] = True
    
    branding = config.setdefault("branding", {})
    branding["showPoweredByGooey"] = False

    config.setdefault("payload", {}).setdefault("variables", {})
    variables = dict(
        update_gui_state_params=dict(state=builder_state, page_slug=page_slug),
    )

    gui.html(
        f"""
<div id="gooey-builder-embed" {div_style}></div>
<script id="gooey-builder-embed-script" src="{settings.WEB_WIDGET_LIB}"></script>
        """
    )
    
    mount_call = "GooeyEmbed.mount(config);" if not on_close_handler else f"""
    config.onClose = function() {{
        document.getElementById("onClose").click();
    }};
    GooeyEmbed.mount(config);
    """
    
    gui.js(
        f"""
async function onload() {{
    await window.waitUntilHydrated;
    if (typeof GooeyEmbed === "undefined" ||
        document.getElementById("gooey-builder-embed")?.children.length)
        return;
    
    GooeyEmbed.setGooeyBuilderVariables = (value) => {{
        config.payload.variables = value;
    }};
    GooeyEmbed.setGooeyBuilderVariables(variables);
    
    {mount_call}
}}

const script = document.getElementById("gooey-builder-embed-script");
if (script) script.onload = onload;
onload();
window.addEventListener("hydrated", onload);

if (typeof GooeyEmbed !== "undefined" && GooeyEmbed.setGooeyBuilderVariables) {{
    GooeyEmbed.setGooeyBuilderVariables(variables);
}}
        """,
        config=config,
        variables=variables,
    )

def render_gooey_builder_inline(page_slug: str, builder_state: dict):
    _render_gooey_builder_common(
        page_slug, 
        builder_state, 
        mode="inline",
        show_run_link=True,
        div_style='style="height: 100%"',
        on_close_handler="onClose"
    )

def render_gooey_builder(page_slug: str, builder_state: dict):
    _render_gooey_builder_common(page_slug, builder_state)
routers/root.py (1)

744-744: Unused variable assignment.

The variable container is assigned on line 744 but is never used in the function. It appears this was meant to be used somewhere but was replaced by direct access to page.

🔎 Apply this diff to remove the unused variable:
-    container = page if page else None
     sidebar_ref = use_sidebar("builder-sidebar", request.session, default_open=False)
widgets/sidebar.py (3)

24-26: Remove or document commented code.

Line 26 contains commented code # self.set_open(value). This suggests uncertainty about whether setting mobile open should also set the regular open state. Either remove this if it's not needed, or add a comment explaining why this relationship is intentionally decoupled.


128-128: Remove unused loop variable.

The loop variable i is declared but never used in the loop body. This is flagged by static analysis.

🔎 Apply this diff:
-    for i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS):
+    for url, label, icon in settings.SIDEBAR_LINKS:

Or if you anticipate using the index in the future:

-    for i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS):
+    for _i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS):

Based on learnings, static analysis hints should be addressed when valid.


44-46: Use pop() instead of checking existence then deleting.

Static analysis correctly identifies that the pattern of checking if a key exists and then deleting it can be simplified using pop().

🔎 Apply this diff:
-        mobile_key = key + ":mobile"
-        if mobile_key in session:
-            del session[mobile_key]
+        session.pop(key + ":mobile", None)

This is more concise and thread-safe.

Based on learnings, static analysis hints should be addressed when valid.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95bb0b4 and 4e23759.

📒 Files selected for processing (5)
  • daras_ai_v2/base.py (5 hunks)
  • daras_ai_v2/gooey_builder.py (1 hunks)
  • daras_ai_v2/settings.py (1 hunks)
  • routers/root.py (3 hunks)
  • widgets/sidebar.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • daras_ai_v2/settings.py
  • daras_ai_v2/base.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • daras_ai_v2/gooey_builder.py
  • routers/root.py
  • widgets/sidebar.py
🧬 Code graph analysis (1)
daras_ai_v2/gooey_builder.py (1)
bots/models/bot_integration.py (2)
  • BotIntegration (163-626)
  • get_web_widget_config (565-592)
🪛 Ruff (0.14.8)
widgets/sidebar.py

46-46: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)


128-128: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (4)
routers/root.py (2)

754-757: LGTM!

The conditional sidebar rendering logic correctly checks for a page instance before calling render_sidebar().


796-808: LGTM!

The Gooey builder mobile button is properly gated for admins, correctly updates the sidebar state, and triggers a rerun to reflect the changes.

widgets/sidebar.py (1)

169-202: LGTM!

The mobile header correctly handles the button click to open the sidebar and triggers a rerun to reflect the state change.

daras_ai_v2/gooey_builder.py (1)

50-52: No action needed. The element with id="onClose" is properly defined in the calling context. In base.py (lines 445-449), a hidden button with id="onClose" is explicitly rendered when the sidebar is open, before the gooey_builder code executes. The callback will successfully locate and trigger this element.

Comment on lines 37 to 47
# Check if this is a fresh page load by comparing timestamps
last_load_time = session.get(f"{key}:last_load_time", 0)
current_time = time.time()

# If more than 1 second has passed since last load, consider it a fresh page load
if current_time - last_load_time > 0.5:
# Fresh page load - clear mobile state
mobile_key = key + ":mobile"
if mobile_key in session:
del session[mobile_key]

# Update the last load time
session[f"{key}:last_load_time"] = current_time
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Time-based page load detection is fragile and unreliable.

The logic uses a 0.5-second threshold to determine if a page load is "fresh" and should clear mobile state. This approach has several issues:

  1. Fast navigations (< 0.5s) won't clear mobile state, causing stale state to persist
  2. Multiple rapid requests could interfere with each other
  3. The threshold is arbitrary and may not work reliably across different network conditions
🔎 Consider a more robust approach:

Use a navigation counter or request ID instead of time-based detection:

def use_sidebar(key: str, session: dict, default_open: bool = True) -> SidebarRef:
    """Create or get a sidebar reference with state management."""
    
    # Use a navigation counter instead of time
    nav_key = f"{key}:nav_count"
    current_nav = session.get(nav_key, 0)
    
    # Increment on each call - fresh page loads will have new session
    # or can be explicitly reset by caller
    if f"{key}:mobile" in session and current_nav == 0:
        # First call in this session - clear mobile state
        del session[f"{key}:mobile"]
    
    session[nav_key] = current_nav + 1
    
    # set the default open state in session here
    session[key] = bool(session.get(key, default_open))
    ref = SidebarRef(
        key=key,
        session=session,
        is_open=bool(session.get(key, default_open)),
        is_mobile_open=bool(session.get(key + ":mobile", False)),
    )
    
    return ref

Alternatively, use a page load marker that the caller explicitly resets, or leverage existing session management patterns in your framework.

🧰 Tools
🪛 Ruff (0.14.8)

46-46: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)

🤖 Prompt for AI Agents
In widgets/sidebar.py around lines 37 to 49, replace the fragile time-based
fresh-load detection with a deterministic navigation counter or request
identifier stored in session; add a nav_key like f"{key}:nav_count", read
current_nav = session.get(nav_key, 0), clear the mobile state only when
current_nav == 0 (or when a new request id differs), then increment and store
session[nav_key] = current_nav + 1, and finally initialize session[key] and the
SidebarRef is_open/is_mobile_open from session; ensure no reliance on time
thresholds and document that callers can reset the nav counter if they need to
force a fresh load.

@anish-work
Copy link
Contributor Author

@devxpy cleaned up, you can review now..

## HUGE HACK HERE
# Check if this is a fresh page load by comparing timestamps
last_load_time = session.get(f"{key}:last_load_time", 0)
current_time = time.time()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devxpy Having trouble to manage mobile open and close state as:

  • We don't want to remember the open state in mobile while in large devices we want to keep it open
    Hence used this workaround works fine but not sure if its reliable though

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (4)
widgets/sidebar.py (1)

34-47: Time-based fresh-load detection remains fragile.

This logic uses a 0.5-second threshold to detect fresh page loads, which can be unreliable under varying network conditions or fast navigations.

routers/root.py (2)

751-755: Include mobile-open state in sidebar open check.

is_builder_sidebar_open only checks sidebar_ref.is_open, ignoring sidebar_ref.is_mobile_open. This can leave a visually open but empty sidebar on mobile when navigating to non-recipe pages.

🔎 Apply this diff:
-    is_builder_sidebar_open = sidebar_ref.is_open
-    if not is_recipe_page and is_builder_sidebar_open:
+    is_builder_sidebar_open = sidebar_ref.is_open or sidebar_ref.is_mobile_open
+    if not is_recipe_page and is_builder_sidebar_open:
         sidebar_ref.set_open(False)
         sidebar_ref.set_mobile_open(False)
         raise gui.RerunException()

842-847: Fix className concatenation (parameter ignored when sidebar is closed).

When the sidebar is closed, the className parameter is dropped. When open, a space is missing before the custom classes.

🔎 Apply this diff:
             with gui.div(
                 id="main-content",
-                className="container-xxl "
-                if not is_builder_sidebar_open
-                else "mx-2 w-100" + className,
+                className=(
+                    "container-xxl " + className
+                    if not is_builder_sidebar_open
+                    else "mx-2 w-100 " + className
+                ),
             ):
daras_ai_v2/base.py (1)

1257-1259: Close both desktop and mobile sidebar states on builder close.

When the builder's onClose fires, only set_open(False) is called. If the sidebar was opened via mobile, is_mobile_open remains True, causing the sidebar to stay open or get out of sync.

🔎 Apply this diff:
             if gui.session_state.pop("onCloseGooeyBuilder", None):
                 sidebar_ref.set_open(False)
+                sidebar_ref.set_mobile_open(False)
                 raise gui.RerunException()
🧹 Nitpick comments (1)
widgets/sidebar.py (1)

42-44: Use pop() instead of if key in dict followed by del.

As suggested by static analysis, this can be simplified.

🔎 Apply this diff:
-        mobile_key = key + ":mobile"
-        if mobile_key in session:
-            del session[mobile_key]
+        session.pop(key + ":mobile", None)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e23759 and 1384057.

📒 Files selected for processing (4)
  • daras_ai_v2/base.py (4 hunks)
  • daras_ai_v2/gooey_builder.py (5 hunks)
  • routers/root.py (3 hunks)
  • widgets/sidebar.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • daras_ai_v2/base.py
  • widgets/sidebar.py
  • daras_ai_v2/gooey_builder.py
  • routers/root.py
🪛 GitHub Actions: Python tests
widgets/sidebar.py

[error] 57-57: Ruff formatting would reformat this file. Please run 'ruff format --diff .' to fix formatting issues.

daras_ai_v2/gooey_builder.py

[error] 65-65: Ruff formatting would reformat this file. Please run 'ruff format --diff .' to fix formatting issues.

🪛 Ruff (0.14.8)
widgets/sidebar.py

44-44: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)

🔇 Additional comments (8)
daras_ai_v2/gooey_builder.py (1)

7-68: Implementation looks good overall.

The inline builder wiring correctly:

  • Sets mode to "inline" with showRunLink enabled
  • Defines a reusable setGooeyBuilderVariables function for state updates
  • Wires the onClose handler to trigger the hidden button click in the parent page
widgets/sidebar.py (1)

66-163: Sidebar layout implementation looks good.

The CSS styling properly handles responsive behavior with media queries for mobile viewports. The layout returns appropriate placeholders for sidebar and main content.

routers/root.py (2)

53-55: Good use of TYPE_CHECKING for forward reference.

The BasePage import under TYPE_CHECKING avoids circular import issues while providing proper type hints.


799-811: Admin-only mobile builder button is well implemented.

The mobile open button is correctly restricted to admin users and triggers a rerun after setting the mobile open state.

daras_ai_v2/base.py (4)

429-431: New render_sidebar method properly delegates to builder rendering.

The sidebar rendering is cleanly delegated to _render_gooey_builder().


1280-1299: Good: Builder state is now properly filtered.

The builder_state is correctly constructed with only the necessary fields (status, request, response, metadata) rather than exposing the entire session_state. This addresses the previous security concern.


1262-1278: Verify state merging doesn't lose important session data.

When update_gui_state is received, the code clears gui.session_state and repopulates it with only fields_to_save() plus the update. Ensure no critical session data (outside of fields_to_save) is inadvertently lost during this operation.


1300-1319: Floating builder button implementation is well done.

The floating button is properly:

  • Hidden on mobile (d-none d-md-block) since mobile uses a header button
  • Positioned fixed at bottom-left with appropriate z-index
  • Styled with hover scale effect for better UX

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
daras_ai_v2/gooey_builder.py (1)

65-68: Fix Ruff formatting issue (already flagged).

The Ruff formatting issue at the end of this function has already been flagged in previous reviews. Please run ruff format . to fix.

widgets/sidebar.py (2)

34-47: Time-based page load detection is fragile (already flagged).

The time-based hack using a 0.5-second threshold to detect fresh page loads has already been flagged as a major issue in previous reviews. This approach is unreliable across different network conditions and navigation patterns.


56-58: Fix Ruff formatting issue (already flagged).

The Ruff formatting issue at line 57 has already been flagged in previous reviews and pipeline failures. Please run poetry run ruff format . to fix.

🧹 Nitpick comments (1)
widgets/sidebar.py (1)

43-44: Consider using pop() for more idiomatic dict key removal.

The pattern if key in dict followed by del dict[key] can be replaced with session.pop(mobile_key, None) for more concise and idiomatic code.

🔎 Apply this diff:
-        if mobile_key in session:
-            del session[mobile_key]
+        session.pop(mobile_key, None)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1384057 and 477aa34.

📒 Files selected for processing (2)
  • daras_ai_v2/gooey_builder.py (4 hunks)
  • widgets/sidebar.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • daras_ai_v2/gooey_builder.py
  • widgets/sidebar.py
🪛 GitHub Actions: Python tests
widgets/sidebar.py

[error] 57-57: Ruff formatting check failed. 1 file would be reformatted by 'ruff format --diff .'. Please run 'poetry run ruff format .' to fix.

🪛 Ruff (0.14.8)
widgets/sidebar.py

44-44: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)

🔇 Additional comments (5)
daras_ai_v2/gooey_builder.py (3)

7-19: LGTM! Inline mode configuration is correct.

The function rename to render_gooey_builder_inline clarifies the inline usage pattern, and the configuration correctly enables inline mode with showRunLink and appropriate branding settings.


30-30: LGTM! Height styling ensures proper container fill.

The height: 100% style ensures the embed widget fills its container, which is essential for the sidebar inline rendering.


50-52: The "onClose" element is correctly implemented. It is rendered in daras_ai_v2/base.py (line 1254) as a hidden button intentionally designed to trigger the onClose event, confirming the handler in gooey_builder.py properly references an existing element.

widgets/sidebar.py (2)

5-28: LGTM! SidebarRef class is well-structured.

The SidebarRef class correctly manages sidebar state by keeping instance attributes synchronized with session storage through its setter methods. The design is clean and appropriate for this use case.


60-163: LGTM! Sidebar layout implementation is comprehensive.

The sidebar_layout function correctly implements responsive sidebar behavior with:

  • Well-defined width constants
  • Comprehensive CSS with proper media queries for mobile (max-width: 990px and 767px)
  • Appropriate sticky positioning for sidebar
  • Correct placeholder elements for composing the two-pane layout

The implementation handles both desktop and mobile states cleanly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
widgets/sidebar.py (4)

23-23: Remove or clarify the commented code.

The commented line # self.set_open(value) suggests uncertainty about whether mobile state should sync desktop state. Either uncomment with a clear reason, or remove if no longer needed.


43-44: Apply Ruff suggestion to simplify dictionary cleanup.

Use .pop(mobile_key, None) instead of checking key existence followed by deletion.

🔎 Proposed fix
-        if mobile_key in session:
-            del session[mobile_key]
+        session.pop(mobile_key, None)

Based on static analysis hints.


50-55: Simplify redundant default_open evaluation.

Line 50 sets session[key] to the resolved boolean value, then lines 54-55 re-evaluate the same logic. Store the result once and reuse it.

🔎 Proposed refactor
     # set the default open state in session here
-    session[key] = bool(session.get(key, default_open))
+    is_open_value = bool(session.get(key, default_open))
+    session[key] = is_open_value
     ref = SidebarRef(
         key=key,
         session=session,
-        is_open=bool(session.get(key, default_open)),
+        is_open=is_open_value,
         is_mobile_open=bool(session.get(key + ":mobile", False)),
     )

69-69: Fix typo in variable name.

sidebar_funtion_classes should be sidebar_function_classes (missing 'c' in "function").

🔎 Proposed fix
-    sidebar_funtion_classes = (
+    sidebar_function_classes = (
         "gooey-sidebar-open"
         if sidebar_ref.is_open or sidebar_ref.is_mobile_open
         else "gooey-sidebar-closed"
     )

And update the reference on line 161:

         sidebar_content_placeholder = gui.div(
-            className=f"d-flex flex-column flex-grow-1 gooey-sidebar {sidebar_funtion_classes}",
+            className=f"d-flex flex-column flex-grow-1 gooey-sidebar {sidebar_function_classes}",
         )
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 477aa34 and 84fec20.

📒 Files selected for processing (1)
  • widgets/sidebar.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • widgets/sidebar.py
🪛 Ruff (0.14.8)
widgets/sidebar.py

44-44: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)

@anish-work anish-work force-pushed the bot_builder_sidebar branch from 41c7ff2 to 23d5c8f Compare January 2, 2026 11:38
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
widgets/sidebar.py (1)

35-47: Time-based page load detection is fragile and unreliable.

As noted in previous reviews, the time-based approach (checking if 0.5 seconds have elapsed) to determine fresh page loads has several issues:

  • Fast navigations may not clear mobile state, causing stale state to persist
  • The threshold is arbitrary and may not work reliably across different network conditions
  • Multiple rapid requests could interfere with each other

Consider using a more deterministic approach such as a navigation counter, request ID, or an explicit session flag that the caller can reset when needed.

routers/root.py (1)

755-762: Sidebar open state check should consider both desktop and mobile states.

Line 758 sets is_builder_sidebar_open = sidebar_ref.is_open, which only checks the desktop open state. If a user opens the builder via the mobile button (setting is_mobile_open to True), the sidebar will be visually open but is_builder_sidebar_open will be False, causing:

  1. The auto-close logic on lines 759-762 won't trigger for mobile-open sidebars on non-recipe pages
  2. Header and main content sizing won't react correctly to mobile-open sidebars

The calculation should consider both flags: is_builder_sidebar_open = sidebar_ref.is_open or sidebar_ref.is_mobile_open

This issue was previously flagged and marked as addressed in commits bb057ce to 4e23759, but the current code still shows the incomplete check.

🧹 Nitpick comments (1)
widgets/sidebar.py (1)

43-44: Use .pop() to simplify dict key removal.

The pattern of checking for key existence followed by del can be replaced with a single .pop() call.

🔎 Proposed simplification
-        if mobile_key in session:
-            del session[mobile_key]
+        session.pop(mobile_key, None)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84fec20 and 23d5c8f.

📒 Files selected for processing (5)
  • daras_ai_v2/base.py
  • daras_ai_v2/gooey_builder.py
  • routers/root.py
  • widgets/sidebar.py
  • workspaces/widgets.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • workspaces/widgets.py
  • daras_ai_v2/base.py
  • daras_ai_v2/gooey_builder.py
  • widgets/sidebar.py
  • routers/root.py
🧬 Code graph analysis (1)
daras_ai_v2/gooey_builder.py (5)
widgets/sidebar.py (4)
  • SidebarRef (5-27)
  • use_sidebar (30-58)
  • set_open (18-19)
  • set_mobile_open (21-23)
workspaces/models.py (1)
  • Workspace (105-458)
daras_ai_v2/base.py (1)
  • current_workspace (1517-1522)
app_users/models.py (1)
  • is_admin (261-262)
bots/models/bot_integration.py (2)
  • get_web_widget_branding (594-602)
  • get_web_widget_config (565-592)
🪛 Ruff (0.14.10)
widgets/sidebar.py

44-44: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test (3.10.12, 1.8.3)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (9)
workspaces/widgets.py (1)

209-209: LGTM! Workspace change now triggers UI refresh.

The added RerunException ensures that after switching workspaces, the current page reruns to reflect the updated workspace context. This aligns well with the new sidebar-driven rendering flow introduced in this PR.

daras_ai_v2/gooey_builder.py (2)

13-21: LGTM! Access control logic is clear and correct.

The function properly checks:

  1. User authentication (non-anonymous)
  2. Admin privileges
  3. Workspace enable_bot_builder flag

This provides appropriate gating for the Gooey Builder feature.


99-100: Configuration for inline builder looks correct.

Setting mode="inline" and showRunLink=True appropriately configures the embedded Gooey widget for inline rendering within the sidebar context.

routers/root.py (2)

843-848: Verify className concatenation includes custom classes in both branches.

The className construction has two branches:

  • When sidebar closed: "container-xxl "
  • When sidebar open: "mx-2 w-100" + className

In the closed branch, the className parameter appears to be missing from the concatenation. If a custom className is passed to page_wrapper, it should be included in both branches.

A previous review flagged this and it was marked as addressed in commits 81218fa to df49769. Please verify the fix is complete.

Expected pattern:

className=(
    "container-xxl " + className
    if not is_builder_sidebar_open
    else "mx-2 w-100 " + className
)

806-812: Builder launcher integration looks clean.

The render_gooey_builder_launcher call is well-positioned in the header and properly passes the request and workspace context. The is_fab_button=False parameter indicates this is the mobile header button (as opposed to the FAB rendered elsewhere).

daras_ai_v2/base.py (4)

434-436: Clean delegation to builder rendering logic.

The new render_sidebar method provides a clear entry point for sidebar content and delegates to the refactored _render_gooey_builder method. This separation of concerns supports the new sidebar-driven architecture.


1236-1241: Auto-close logic correctly checks both open states.

The condition if sidebar_ref.is_open or sidebar_ref.is_mobile_open: properly considers both desktop and mobile sidebar states, ensuring the builder sidebar is closed (and both flags are cleared) when navigating away from run/preview tabs. This prevents stale sidebar state from persisting across different tab contexts.


1252-1269: Builder state update handling looks correct.

The update_gui_state merge logic:

  1. Preserves only fields relevant to the recipe (via fields_to_save())
  2. Sets the --has-request-changed flag to indicate modifications
  3. Merges in the updates from the builder widget
  4. Clears and reinitializes session_state

This ensures that builder-driven changes are properly integrated into the session while maintaining consistency with the recipe's expected state structure.


1270-1290: Filtered state prevents unintended data exposure to the widget.

The inline builder now receives a filtered builder_state containing only:

  • Status fields (error_msg, run_status, run_time)
  • Request fields (via extract_model_fields)
  • Response fields (via extract_model_fields)
  • Metadata (title, description)

This is a significant improvement over sending the entire gui.session_state, as it avoids inadvertently exposing unrelated or sensitive session data to the client-side widget.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
routers/root.py (2)

845-850: className concatenation still broken despite past review feedback.

The className parameter passed to page_wrapper is either dropped entirely (when sidebar is closed) or concatenated without proper spacing (when sidebar is open). This was flagged in a previous review (marked as "✅ Addressed in commits 81218fa to df49769") but the fix is not present in the current code.

Current behavior:

  • Sidebar closed: "container-xxl " → custom className is lost
  • Sidebar open: "mx-2 w-100" + className → missing space separator
Apply the previously suggested fix
-            with gui.div(
-                id="main-content",
-                className="container-xxl "
-                if not is_builder_sidebar_open
-                else "mx-2 w-100" + className,
-            ):
+            with gui.div(
+                id="main-content",
+                className=(
+                    "container-xxl " + className
+                    if not is_builder_sidebar_open
+                    else "mx-2 w-100 " + className
+                ),
+            ):

754-762: Mobile sidebar state is still ignored despite past review feedback.

Line 758 only checks sidebar_ref.is_open but ignores sidebar_ref.is_mobile_open. This was flagged in a previous review (marked as "✅ Addressed in commits bb057ce to 4e23759") but the fix is not present in the current code.

When the sidebar is opened via the mobile button, is_mobile_open is True but is_open remains False. This causes:

  • The auto-close logic on line 759 to not trigger on non-recipe pages
  • Layout calculations in lines 776-779 and 845-850 to use the wrong container classes
  • Inconsistent rendering between mobile and desktop states
Apply the previously suggested fix
-    is_builder_sidebar_open = sidebar_ref.is_open
-    if not is_recipe_page and (is_builder_sidebar_open):
+    is_builder_sidebar_open = sidebar_ref.is_open or sidebar_ref.is_mobile_open
+    if not is_recipe_page and is_builder_sidebar_open:
         sidebar_ref.set_open(False)
         sidebar_ref.set_mobile_open(False)
         raise gui.RerunException()
🧹 Nitpick comments (1)
daras_ai_v2/base.py (1)

1232-1241: Consider adding explicit default_open parameter for consistency.

Line 1233 calls use_sidebar without a default_open parameter, while routers/root.py line 755 explicitly passes default_open=False. Since both use the same session key "builder-sidebar", they reference the same sidebar state, so this works correctly. However, adding the explicit parameter improves code clarity and consistency.

Optional consistency improvement
-        sidebar_ref = use_sidebar("builder-sidebar", self.request.session)
+        sidebar_ref = use_sidebar("builder-sidebar", self.request.session, default_open=False)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23d5c8f and 6cd9728.

📒 Files selected for processing (2)
  • daras_ai_v2/base.py
  • routers/root.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • routers/root.py
  • daras_ai_v2/base.py
🧬 Code graph analysis (1)
routers/root.py (3)
daras_ai_v2/gooey_builder.py (1)
  • render_gooey_builder_launcher (23-71)
widgets/sidebar.py (4)
  • sidebar_layout (67-164)
  • use_sidebar (30-58)
  • set_open (18-19)
  • set_mobile_open (21-23)
workspaces/widgets.py (1)
  • get_current_workspace (193-204)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test (3.10.12, 1.8.3)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (3)
routers/root.py (1)

806-814: Proper workspace access gating implemented.

The code correctly checks request.user before calling get_current_workspace, preventing errors when the user is not authenticated. This aligns with the PR objective that mentions "fix: check use before fetching workspace."

daras_ai_v2/base.py (2)

434-436: Clean public API for sidebar rendering.

The render_sidebar method provides a clear entry point for rendering the builder sidebar, properly delegating to the internal implementation.


1252-1291: Builder state properly filtered to prevent data exposure.

The inline builder rendering correctly filters the session state to include only necessary fields (status, request model fields, response model fields, and metadata) instead of passing the entire gui.session_state. This addresses the security concern raised in a previous review about inadvertently exposing sensitive or unrelated data to the client widget.

The state update handling (lines 1253-1269) also properly preserves only the fields that should be saved using fields_to_save(), ensuring a controlled merge of updates.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI Agents
In @routers/root.py:
- Around line 758-762: The check for whether the builder sidebar is open only
inspects sidebar_ref.is_open but must also consider sidebar_ref.is_mobile_open;
update the is_builder_sidebar_open assignment to combine both states (e.g., OR
them) so that mobile-opened sidebars are detected, then ensure both
sidebar_ref.set_open(False) and sidebar_ref.set_mobile_open(False) are still
called before raising gui.RerunException() to fully close the sidebar UI.
- Around line 845-850: The className concatenation in gui.div for id
"main-content" drops the passed-in className when is_builder_sidebar_open is
False and omits a separating space when True; update the className expression in
the gui.div call (the main-content element) to always start with the base
"container-xxl" (or "mx-2 w-100" when sidebar open) and then append the external
variable className prefixed by a space if className is non-empty so custom
classes are preserved and properly separated in both branches.
🧹 Nitpick comments (1)
routers/root.py (1)

754-754: Simplify redundant assignment.

The ternary operator is unnecessary since page is already Optional[BasePage] and defaults to None.

🔎 Proposed simplification
-    container = page if page else None
+    container = page
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6cd9728 and 23b35eb.

📒 Files selected for processing (1)
  • routers/root.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • routers/root.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (3)
routers/root.py (3)

10-10: LGTM!

The imports are well-organized, with proper use of TYPE_CHECKING to handle forward references and avoid circular imports.

Also applies to: 50-50, 52-61


768-844: LGTM!

The main pane content structure properly integrates the Gooey Builder launcher and handles both authenticated and anonymous user states correctly.


851-854: LGTM!

The context manager correctly yields the current workspace and renders footer/scripts in the proper order.

@anish-work anish-work force-pushed the bot_builder_sidebar branch from 9a5252f to 689d02f Compare January 8, 2026 07:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @daras_ai_v2/gooey_builder.py:
- Line 33: Wrap calls to BotIntegration.objects.get using
settings.GOOEY_BUILDER_INTEGRATION_ID in a try-except that catches
BotIntegration.DoesNotExist (e.g., around the BotIntegration.objects.get(...) at
the top-level and the second call later), and handle the failure by logging a
clear error and either using a safe fallback or aborting startup; alternatively,
perform an existence check (BotIntegration.objects.filter(id=...).exists())
before calling .get() to avoid the exception. Ensure you reference the exact
symbol BotIntegration.objects.get and settings.GOOEY_BUILDER_INTEGRATION_ID when
applying the fix so both occurrences are protected.
- Line 30: Remove the redundant local import of BotIntegration inside
gooey_builder.py (the line "from bots.models import BotIntegration" shown in the
diff); rely on the existing top-level import already present at the top of the
file, delete the duplicate import line and ensure no other references depend on
a local import within the function or block where it was added.

In @routers/root.py:
- Around line 846-851: The className concatenation in the gui.div call for
id="main-content" can produce a missing space when is_builder_sidebar_open is
true because it uses "mx-2 w-100" + className; change the concatenation to
ensure a space separator (e.g., prepend a leading space before className or use
string interpolation/join) so the final className always has a space between
"w-100" and any additional classes; update the expression used in the className
parameter within the gui.div call accordingly (referencing gui.div,
id="main-content", className and is_builder_sidebar_open).
- Around line 52-58: Remove the unused imports Workspace and
SESSION_SELECTED_WORKSPACE from the top of routers.root; keep only the used
symbols (get_current_workspace, global_workspace_selector,
workspace_selector_link) imported from workspaces.widgets and delete the direct
import of Workspace from workspaces.models and the constant
SESSION_SELECTED_WORKSPACE to eliminate unused-import warnings.

In @widgets/sidebar.py:
- Around line 39-44: The comment and code disagree on the threshold (comment
says "1 second" but the code uses 0.5) and the deletion uses if/del; update the
comment to state "0.5 seconds" (or change the numeric threshold to 1.0 if you
prefer 1s) so they match, and replace the if/membership + del pattern with
session.pop(mobile_key, None) using the existing symbols current_time,
last_load_time, mobile_key and session to clear the mobile state.
- Line 69: There is a typo in the variable name sidebar_funtion_classes — rename
it to sidebar_function_classes throughout the module (change the declaration of
sidebar_funtion_classes to sidebar_function_classes) and update every usage site
(including the code referenced around the previous usage at line 161) to use the
new sidebar_function_classes identifier so all references remain consistent.
🧹 Nitpick comments (3)
routers/root.py (1)

10-11: Top-level import may increase server import time.

The PR description specifically mentions checking that "changes have not increased server import time" and suggests importing heavy libraries inside functions. Consider whether importing render_gooey_builder_launcher at module level is necessary, or if it could be deferred to where it's used (inside page_wrapper).

widgets/sidebar.py (2)

30-58: Time-based fresh page detection is fragile.

The "HUGE HACK" comment is apt—relying on elapsed time (0.5s) to detect fresh page loads can fail under slow network conditions, server latency, or aggressive polling. Consider if there's a more reliable mechanism (e.g., a dedicated session flag cleared on navigation, or checking a request header).


21-23: Commented-out code suggests incomplete logic.

The commented line # self.set_open(value) in set_mobile_open might indicate that mobile and desktop open states should be synchronized. Clarify the intended behavior or remove the dead comment.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a5252f and 689d02f.

📒 Files selected for processing (4)
  • daras_ai_v2/base.py
  • daras_ai_v2/gooey_builder.py
  • routers/root.py
  • widgets/sidebar.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • widgets/sidebar.py
  • daras_ai_v2/base.py
  • daras_ai_v2/gooey_builder.py
  • routers/root.py
🧬 Code graph analysis (2)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder_inline (74-151)
  • render_gooey_builder_launcher (23-71)
widgets/sidebar.py (4)
  • sidebar_layout (67-164)
  • use_sidebar (30-58)
  • set_open (18-19)
  • set_mobile_open (21-23)
daras_ai_v2/gooey_builder.py (5)
widgets/sidebar.py (4)
  • SidebarRef (5-27)
  • use_sidebar (30-58)
  • set_open (18-19)
  • set_mobile_open (21-23)
workspaces/models.py (1)
  • Workspace (105-458)
daras_ai_v2/base.py (1)
  • current_workspace (1518-1523)
app_users/models.py (1)
  • is_admin (261-262)
bots/models/bot_integration.py (1)
  • get_web_widget_branding (594-602)
🪛 Ruff (0.14.10)
widgets/sidebar.py

44-44: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (8)
routers/root.py (1)

759-762: Raising RerunException inside a context manager may cause unexpected behavior.

When page_wrapper is used as a context manager via with page_wrapper(...), raising RerunException here will exit the context before the yielded block executes. This could be intentional, but verify that callers handle this gracefully and that no cleanup logic is bypassed.

daras_ai_v2/base.py (4)

434-436: LGTM!

Clean delegation pattern for sidebar rendering. The new render_sidebar method provides a clear public API for rendering the Gooey builder in the sidebar context.


1253-1269: Verify session state clearing doesn't cause data loss.

The code clears gui.session_state entirely (line 1268) and repopulates with new_state. Ensure this doesn't discard important transient state (e.g., UI widget states, form inputs) that should persist across updates.


1273-1291: Builder state assembly looks correct.

The builder_state dict properly extracts status, request, and response fields using extract_model_fields and includes metadata. The inline rendering with sidebar_ref integration follows the expected pattern.


1236-1241: Early return after closing sidebar prevents unintended rendering.

The logic correctly closes the builder sidebar when navigating away from run/preview tabs and raises RerunException to prevent stale UI. This ensures consistent state.

daras_ai_v2/gooey_builder.py (3)

13-20: Access control logic looks correct.

The can_launch_gooey_builder function properly gates access: anonymous users are blocked, admins always pass, and regular users require the workspace's enable_bot_builder flag. This aligns with the external snippet showing enable_bot_builder on Workspace.


80-92: Clever onClose handling via hidden button.

Using a hidden submit button to bridge the JavaScript onClose callback with server-side state management is a pragmatic solution for the Gooey GUI framework. The pop-and-check pattern ensures the close action is processed once.


117-151: JavaScript hydration and variable wiring is well-structured.

The JS code properly handles:

  1. Waiting for hydration before mounting
  2. Dynamic variable updates post-mount via setGooeyBuilderVariables
  3. The onClose bridge to the hidden button

The event listener for hydrated ensures the widget mounts correctly on re-renders.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @daras_ai_v2/gooey_builder.py:
- Around line 96-99: The BotIntegration lookup in the gooey_builder flow is
missing exception handling: wrap the
BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID) call in a
try/except that catches BotIntegration.DoesNotExist, log or handle the missing
integration (as done in render_gooey_builder_launcher) and return an appropriate
response instead of letting the exception bubble; ensure get_web_widget_config
is only called when the BotIntegration instance (bi) exists and include the same
error handling/logging behavior used in render_gooey_builder_launcher to keep
behavior consistent.
🧹 Nitpick comments (4)
widgets/sidebar.py (2)

30-57: Minor redundancy in session state initialization.

Line 49 sets session[key], then line 53 reads it back with session.get(key, default_open). Since the key is already set, the second call will always return session[key], making the default parameter unused.

Suggested simplification
     # set the default open state in session here
-    session[key] = bool(session.get(key, default_open))
+    is_open = bool(session.get(key, default_open))
+    session[key] = is_open
     ref = SidebarRef(
         key=key,
         session=session,
-        is_open=bool(session.get(key, default_open)),
+        is_open=is_open,
         is_mobile_open=bool(session.get(key + ":mobile", False)),
     )

66-72: Consider renaming sidebar_function_classes for clarity.

The variable name sidebar_function_classes is misleading since it represents open/closed state, not function. A name like sidebar_state_classes would be clearer.

routers/root.py (1)

770-781: Consider extracting header rendering logic for readability.

The deeply nested context managers (4+ levels) make the header structure difficult to follow. Consider extracting the header content into a separate helper function.

daras_ai_v2/gooey_builder.py (1)

13-20: Return type may be inconsistent.

Line 20 returns current_workspace and current_workspace.enable_bot_builder, which could return None, False, or the enable_bot_builder value. For a function declared to return bool, consider an explicit boolean cast.

Suggested fix
-    return current_workspace and current_workspace.enable_bot_builder
+    return bool(current_workspace and current_workspace.enable_bot_builder)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 689d02f and 6e61641.

📒 Files selected for processing (3)
  • daras_ai_v2/gooey_builder.py
  • routers/root.py
  • widgets/sidebar.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • widgets/sidebar.py
  • daras_ai_v2/gooey_builder.py
  • routers/root.py
🧠 Learnings (1)
📚 Learning: 2025-06-16T11:43:04.972Z
Learnt from: nikochiko
Repo: GooeyAI/gooey-server PR: 703
File: workspaces/widgets.py:161-166
Timestamp: 2025-06-16T11:43:04.972Z
Learning: In workspaces/widgets.py header links rendering, the gui.columns([2, 10]) layout should be maintained consistently even when no icon exists, leaving empty space in the first column for visual alignment purposes. This prioritizes consistent alignment over space optimization.

Applied to files:

  • routers/root.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (9)
widgets/sidebar.py (2)

5-27: LGTM - SidebarRef class provides clean state encapsulation.

The class correctly manages sidebar open states with proper session persistence through set_open and set_mobile_open methods.


87-136: Verify z-index consistency between desktop and mobile states.

The sidebar uses z-index: 999 at line 93 but z-index: 100 in the mobile media query at line 121. This could cause the mobile sidebar to appear behind other fixed/sticky elements that have z-index between 100 and 999.

routers/root.py (5)

10-11: LGTM - Import additions are appropriate.

The new imports for builder launcher and sidebar components are correctly placed.


752-760: LGTM - Sidebar initialization and auto-close logic is well-structured.

The pattern of closing the builder sidebar on non-recipe pages and triggering a rerun provides clean state management and consistent UX.


804-813: LGTM - Builder launcher integration in recipe pages.

The launcher is correctly placed in the header for recipe pages with appropriate workspace context retrieval.


844-850: LGTM - Main content container adapts to sidebar state.

The conditional className logic correctly switches between centered container and full-width layout based on whether the builder sidebar is open.


856-858: LGTM - Mobile search button layout adjustment.

Removing flex-grow-1 aligns with the new header layout that includes the builder launcher.

daras_ai_v2/gooey_builder.py (2)

82-94: LGTM - Clean pattern for bridging JS events to server-side handling.

The hidden button approach for handling the widget's onClose event via form submission is a practical solution for the server-rendered architecture.


48-49: Validate and sanitize photo_url before HTML embedding.

The photo_url is embedded directly in the src attribute of an <img> tag without validation. Since it derives from self.photo_url (a BotIntegration model field) and can be overridden via web_config_extras, a malicious actor could inject harmful protocols (e.g., javascript: or data:) to create an XSS vector. Validate that the URL uses only safe protocols (http/https) before embedding, or use a URL validation utility.

Comment on lines 96 to 99
bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
config = bi.get_web_widget_config(
hostname="gooey.ai", target="#gooey-builder-embed"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing exception handling for BotIntegration lookup.

Unlike render_gooey_builder_launcher (lines 32-35), this function doesn't catch BotIntegration.DoesNotExist. If the integration is missing, this will raise an unhandled exception.

Suggested fix
+    try:
+        bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
+    except BotIntegration.DoesNotExist:
+        return
-    bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
     config = bi.get_web_widget_config(
         hostname="gooey.ai", target="#gooey-builder-embed"
     )
🤖 Prompt for AI Agents
In @daras_ai_v2/gooey_builder.py around lines 96 - 99, The BotIntegration lookup
in the gooey_builder flow is missing exception handling: wrap the
BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID) call in a
try/except that catches BotIntegration.DoesNotExist, log or handle the missing
integration (as done in render_gooey_builder_launcher) and return an appropriate
response instead of letting the exception bubble; ensure get_web_widget_config
is only called when the BotIntegration instance (bi) exists and include the same
error handling/logging behavior used in render_gooey_builder_launcher to keep
behavior consistent.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @daras_ai_v2/gooey_builder.py:
- Around line 97-100: Wrap the BotIntegration lookup in a try/except that
catches BotIntegration.DoesNotExist (the same pattern used in
render_gooey_builder_launcher) so the code that calls
BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID) does not
raise—on exception return the same graceful fallback/response used by
render_gooey_builder_launcher instead of letting the error bubble, and only call
bi.get_web_widget_config(...) when the lookup succeeds.
🧹 Nitpick comments (1)
daras_ai_v2/gooey_builder.py (1)

13-74: Reorder functions to follow coding guideline.

The coding guideline requires reverse topological order: entry points at the top, dependencies below. Currently, can_launch_gooey_builder (dependency) appears before render_gooey_builder_launcher (which calls it).

Per the guideline, render_gooey_builder_launcher should come first, followed by can_launch_gooey_builder.

As per coding guidelines: Format code in reverse topological order: place the main() function at the top and dependencies below it.

♻️ Suggested reordering

Move render_gooey_builder_launcher (lines 23-74) above can_launch_gooey_builder (lines 13-21), and move render_gooey_builder_inline to the top since it's also an entry point.

Suggested order:

  1. render_gooey_builder_launcher (entry point)
  2. render_gooey_builder_inline (entry point)
  3. can_launch_gooey_builder (helper/dependency)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e61641 and a0a2277.

📒 Files selected for processing (1)
  • daras_ai_v2/gooey_builder.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • daras_ai_v2/gooey_builder.py
🧬 Code graph analysis (1)
daras_ai_v2/gooey_builder.py (4)
widgets/sidebar.py (4)
  • SidebarRef (5-27)
  • use_sidebar (30-57)
  • set_open (18-19)
  • set_mobile_open (21-23)
workspaces/models.py (1)
  • Workspace (105-458)
app_users/models.py (1)
  • is_admin (261-262)
bots/models/bot_integration.py (3)
  • BotIntegration (163-626)
  • get_web_widget_branding (594-602)
  • get_web_widget_config (565-592)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (4)
daras_ai_v2/gooey_builder.py (4)

1-10: LGTM!

The imports and default constant are appropriate for the new sidebar-driven builder functionality.


13-21: LGTM!

The access control logic correctly gates builder access: anonymous users are blocked, admins have full access, and workspace users require the enable_bot_builder feature flag.


92-95: LGTM!

The onClose handler correctly closes both desktop and mobile sidebar states, matching the PR objective to "close both desktop and mobile sidebar states when builder is closed."


48-50: HTML in gui.button() label is not rendered as HTML.

The photo_url is validated by CustomURLField to only allow http:// or https:// URLs, preventing injection of arbitrary content. Additionally, gooey-gui 0.6.0 does not support HTML rendering in button label parameters—the label is treated as plain text. If HTML rendering is intended, use gui.html() or a dedicated image component instead.

Likely an incorrect or invalid review comment.

Comment on lines 97 to 100
bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
config = bi.get_web_widget_config(
hostname="gooey.ai", target="#gooey-builder-embed"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing exception handling for BotIntegration lookup.

Unlike render_gooey_builder_launcher (lines 33-35), this function doesn't catch BotIntegration.DoesNotExist. If the integration is missing, this will raise an unhandled exception instead of gracefully returning.

🔧 Proposed fix to add exception handling
-    bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
+    try:
+        bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
+    except BotIntegration.DoesNotExist:
+        return
     config = bi.get_web_widget_config(
         hostname="gooey.ai", target="#gooey-builder-embed"
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
config = bi.get_web_widget_config(
hostname="gooey.ai", target="#gooey-builder-embed"
)
try:
bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
except BotIntegration.DoesNotExist:
return
config = bi.get_web_widget_config(
hostname="gooey.ai", target="#gooey-builder-embed"
)
🤖 Prompt for AI Agents
In @daras_ai_v2/gooey_builder.py around lines 97 - 100, Wrap the BotIntegration
lookup in a try/except that catches BotIntegration.DoesNotExist (the same
pattern used in render_gooey_builder_launcher) so the code that calls
BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID) does not
raise—on exception return the same graceful fallback/response used by
render_gooey_builder_launcher instead of letting the error bubble, and only call
bi.get_web_widget_config(...) when the lookup succeeds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants